<?php 

declare(strict_types=1);

namespace WanZ\App\Utils;

use Closure;
use Exception;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Handler\CurlHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;
use Hyperf\Contract\StdoutLoggerInterface;
use Hyperf\Di\Annotation\Inject;
use Hyperf\Context\Context;
use WanZ\App\Utils\Prometheus\PrometheusFactory;
use MoChat\Framework\Constants\ErrorCode;
use MoChat\Framework\Exception\CommonException;

/**
 * Class HttpRequest
 * @package WanZ\App\Utils
 * 
 */
class HttpRequest
{
    private const RETRY_NUMBER   = 3;     //最大重试次数
    private const DELAY_TIME     = 10;    //延迟时间；默认[重试次数*1000]微秒
    private const TIMEOUT_TIME   = 3.14;  //超时时间
    public $requestHttp = null;

    /**
     * @Inject
     * @var StdoutLoggerInterface
     */
    private $logger;

    public function __construct()
    {
        if (is_null($this->requestHttp)) {
            $stack = HandlerStack::create(new CurlHandler());
            $stack->push(Middleware::retry($this->retryDecider(), $this->retryDelay()));
            $this->requestHttp = new Client(['handler' => $stack]);
        }
    }

    /**
     * json格式的post请求
     *
     * @param string $url
     * @param array $params
     * @param float $customTimeOut
     * @return mixed
     * @throws CommonException
     * @throws GuzzleException
     */
    public function postJson(string $url, array $params, float $customTimeOut = 0.0)
    {
        try {
            $timeOut = self::TIMEOUT_TIME;
            if ($customTimeOut > 0) {
                $timeOut = $customTimeOut;
            }

            $headers = $this->getJsonHeader();

            if (isset($params['headers']) && !empty($params['headers'])) {
                $headers = array_merge($headers, $params['headers']);
                unset($params['headers']);
            }

            $response = $this->requestHttp->request('POST', $url, [
                'timeout' => $timeOut,
                'headers' => $headers,
                'json'    => $params,
            ]);

            return $this->parseResponseData($response->getBody()->getContents());
        } catch (Exception $exception) {
            $this->logger->error('WzPostJsonRequestError', [
                'requestUrl'  => $url,
                'requestData' => json_encode(['reqData' => $params]),
                'errMsg'      => $exception->getMessage(),
                'site'        => $exception->getFile().$exception->getLine()
            ]);

            PrometheusFactory::brother()->addErrorNumToBrotherService($url);
            throw new CommonException($exception->getCode(), $exception->getMessage());
        }
    }

    /**
     * 文件上传格式的post请求
     *
     * @param string $url
     * @param array $params
     * @param float $customTimeOut
     * @return mixed
     * @throws CommonException
     * @throws GuzzleException
     */
    public function postMultipart(string $url, array $params, float $customTimeOut = 0.0)
    {
        try {
            $timeOut = self::TIMEOUT_TIME;
            if ($customTimeOut > 0) {
                $timeOut = $customTimeOut;
            }

            $headers = [];
            $headers = ['x-b3-traceid' => Context::get('x-b3-traceid', '')];
            if (isset($params[0]['headers']) && !empty($params[0]['headers'])) {
                $headers = array_merge($headers, $params[0]['headers']);
                unset($params[0]['headers']);
            }

            $response = $this->requestHttp->request('POST', $url, [
                'timeout'   => $timeOut,
                'headers'   => $headers,
                'multipart' => $params,
            ]);

            return $this->parseResponseData($response->getBody()->getContents());
        } catch (Exception $exception) {
            $this->logger->error('wzPostMultipartRequestError', [
                'requestUrl'  => $url,
                'requestData' => json_encode($params),
                'errMsg'      => $exception->getMessage(),
                'site'        => $exception->getFile().$exception->getLine()
            ]);

            PrometheusFactory::brother()->addErrorNumToBrotherService($url);
            throw new CommonException($exception->getCode(), $exception->getMessage());
        }
    }

    /**
     * get方式请求
     *
     * @param string $url
     * @param array $params
     * @return mixed
     * @throws CommonException
     * @throws GuzzleException
     */
    public function get(string $url, array $params)
    {
        try {
            $response = $this->requestHttp->request('GET', $url, [
                'timeout' => self::TIMEOUT_TIME,
                'headers' => $this->getJsonHeader(),
                'query'   => $params
            ]);

            return $this->parseResponseData($response->getBody()->getContents());
        } catch (Exception $exception) {
            $this->logger->error('WzGetRequestError', [
                'requestUrl'  => $url,
                'requestData' => json_encode($params),
                'errMsg'      => $exception->getMessage(),
                'site'        => $exception->getFile().$exception->getLine()
            ]);   

            PrometheusFactory::brother()->addErrorNumToBrotherService($url);
            throw new CommonException($exception->getCode(), $exception->getMessage());
        }
    }

    /**
     * 获取json格式headers头
     *
     * @return string[]
     */
    private function getJsonHeader(): array
    {
        return [
            'Content-Type' => 'application/json;charset=utf-8',
            'x-b3-traceid' => Context::get('x-b3-traceid', '')
        ];
    }

    /**
     * 解析响应数据
     *
     * @param $data
     * @return mixed
     */
    private function parseResponseData($data)
    {
        return json_decode($data, true);
    }

    /**
     * 重试条件决策方法 true：表示重试；false：表示结束重试
     *
     * @return Closure()
     */
    protected function retryDecider(): Closure
    {
        return function ($retries, Request $request, Response $response = null, $exception = null) {
            if ($retries >= self::RETRY_NUMBER) {
                return false;
            }

            if ($exception instanceof ConnectException || $exception instanceof RequestException) {
                return true;
            }

            if ($response) {
                if ($response->getStatusCode() >= 500) {
                    return true;
                }
            }

            return false;
        };
    }

    /**
     * 每次重试的间隔时间 usleep($microseconds)
     * @return Closure
     */
    protected function retryDelay(): Closure
    {
        return function($retries) {
            if (self::DELAY_TIME > 0) {
                return $retries * self::DELAY_TIME;
            }

            return $retries;
        };
    }
}
